/*
 *   Copyright 2012-present OSBI Ltd
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

// Packages
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import $ from 'jquery';
import 'jquery-ui/ui/widgets/droppable.js';
import { cloneDeep, defer, find, groupBy, isEmpty } from 'lodash';
import { Grid, Row, Col } from 'react-flexbox-grid';
import {
  Button,
  ButtonGroup,
  Card,
  Classes,
  Dialog,
  Icon,
  Intent,
  NonIdealState
} from '@blueprintjs/core';

// Actions
import { actionCreators } from '../../../actions';

// DnD Actions
import {
  dragDisableMeasure,
  dragEnableMeasure,
  copyMeasure,
  reorderMeasure,
  removeMeasure,
  setMeasuresDetails,
  clearMeasures
} from './AxisMeasures/actions';
import {
  dragDisableLevel,
  dragEnableLevel,
  dragEnableLevelFromAxis,
  dragEnableLevelFromHierarchy,
  copyLevel,
  reorderHierarchy,
  moveHierarchy,
  removeHierarchy,
  removeLevel,
  setMembers,
  swapAxes,
  clearDimensions
} from './AxisDimensions/actions';

// DnD
import TreeMeasures from './TreeMeasures';
import TreeDimensions from './TreeDimensions';
import AxisMeasures from './AxisMeasures';
import AxisDimensions from './AxisDimensions';

// DnD Selections
import SelectLevels from './AxisDimensions/selections/SelectLevels';

// UI
import { WarningAlert } from '../../UI';

// Utils
import { Settings } from '../../../utils';
import { MEASURES, ROWS, COLUMNS, FILTER } from '../../../utils/constants';

// Styles
import './QueryDesignerDialog.css';

// Constants
const { QUERY_DESIGNER } = Settings.PLUGINS;
const isDisabledFlag = '__react-saiku-ui-disable-dev-logs';

class QueryDesignerDialog extends Component {
  state = {
    selectedHierGroup: {},
    selectedHierGroupText: '',
    selectedLevels: [],
    isShowSelectLevels: false,
    selectNavigationTab: QUERY_DESIGNER.SELECT_NAVIGATION_TAB,
    dialogHeight: 500,
    isDisabledActionBtn: true,
    isOpenClearAllAxesAlert: false
  };

  constructor(props) {
    super(props);

    const { cubes, query } = this.props;
    const selectedCube = `${query.cube.connection}/${query.cube.catalog}/${
      query.cube.schema === '' || query.cube.schema === null
        ? 'null'
        : query.cube.schema
    }/${encodeURIComponent(query.cube.name)}`;
    const cube = find(cubes, { key: `cube.${selectedCube}` });

    this.measures = groupBy(cube.measures, 'measureGroup');
    this.dimensions = cube.dimensions;
  }

  componentWillMount() {
    const { workspace } = this.props;
    const height = $('body').height() / 2 + $('body').height() / 3;
    let newState = {};

    if (workspace.viewState === 'NEW_QUERY') {
      newState = {
        ...workspace.plugins.queryDesigner,
        measuresData: this.measures,
        dimensionsData: this.dimensions,
        dialogHeight: height
      };

      this.setState(newState);
    } else if (workspace.viewState === 'EDIT_QUERY') {
      newState = {
        ...workspace.plugins.queryDesigner,
        dialogHeight: height,
        isDisabledActionBtn: false
      };

      this.setState(newState);
    } else {
      // workspace.viewState = OPEN_QUERY
    }
  }

  componentDidMount() {
    this.makeRemoveItemDroppable();
  }

  componentDidUpdate(prevProps, prevState) {
    this.makeRemoveItemDroppable();
  }

  componentWillUnmount() {
    if (this.$skuCardNode) {
      this.$skuCardNode.droppable('destroy');
    }
  }

  handleChangeNavigationTab(tab) {
    this.setState({
      selectNavigationTab: tab,
      isDisabledActionBtn: this.disableActionBtn()
    });
  }

  makeRemoveItemDroppable() {
    defer(() => {
      this.$skuCardNode = $('.sku-card');

      this.$skuCardNode.droppable({
        tolerance: 'pointer',
        over: (event, ui) => {
          const $levelItem = ui.draggable;
          const dragSourceId = $levelItem.attr('dragsourceid');

          if (dragSourceId) {
            this.$skuCardNode.find('.sku-card-body').hide();
            this.$skuCardNode.find('.sku-card-remove-item').show();
          } else {
            return;
          }
        },
        out: (event, ui) => {
          const $levelItem = ui.draggable;
          const dragSourceId = $levelItem.attr('dragsourceid');

          if (dragSourceId) {
            this.$skuCardNode.find('.sku-card-body').show();
            this.$skuCardNode.find('.sku-card-remove-item').hide();
          } else {
            return;
          }
        },
        drop: (event, ui) => {
          const $levelItem = ui.draggable;
          const dragSourceId = $levelItem.attr('dragsourceid');
          const dragSourceIndex = $levelItem.attr('dragsourceindex');
          const source = {
            draggableId: dragSourceId,
            index: dragSourceIndex
          };

          this.$skuCardNode.find('.sku-card-body').show();
          this.$skuCardNode.find('.sku-card-remove-item').hide();

          if (dragSourceId === MEASURES) {
            const measureName = $levelItem.attr('measure');

            this.handleRemoveMeasure(measureName, source);
          } else if (
            dragSourceId === ROWS ||
            dragSourceId === COLUMNS ||
            dragSourceId === FILTER
          ) {
            this.handleRemoveHierarchyAll(source);
          } else {
            return;
          }
        }
      });
    });
  }

  disableActionBtn = () => {
    if (
      !isEmpty(this.state.axes[MEASURES].items) &&
      !isEmpty(this.state.axes[ROWS].items)
    ) {
      return false;
    }

    if (
      !isEmpty(this.state.axes[ROWS].items) &&
      !isEmpty(this.state.axes[COLUMNS].items)
    ) {
      return false;
    }

    return true;
  };

  /**
   * A drag has ended. It is the responsibility of this responder to
   * synchronously apply changes that has resulted from the drag
   *
   * responders:
   *
   *   {
   *     data: {
   *       name: 'Promotion Sales',
   *       type: 'EXACT'
   *     },
   *     destination: {
   *       droppableId: 'MEASURES',
   *       index: 1
   *     },
   *     options: {
   *       data: 'Sales'
   *     },
   *     source: {
   *       draggableId: 'TREE_MEASURES', index: '5'
   *     },
   *     type: 'MEASURES'
   *   }
   */
  onDragEnd = result => {
    const { type, data, source, destination, options } = result;

    if (typeof window !== 'undefined' && !window[isDisabledFlag]) {
      console.log(
        `Type: ${type}\nSource: ${source.draggableId}\nDestination: ${destination.droppableId}`,
        result
      );
    }

    if (!destination) {
      return;
    }

    if (destination.index === -1) {
      return;
    }

    if (
      source.draggableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return;
    }

    if (type === MEASURES) {
      if (source.draggableId === destination.droppableId) {
        this.setState({
          axes: reorderMeasure(
            this.state.axes,
            this.state.axes[destination.droppableId].items,
            source,
            destination
          )
        });
      } else {
        this.setState({
          measuresData: dragDisableMeasure(
            this.state.measuresData,
            source,
            options
          ),
          axes: copyMeasure(this.state.axes, data, destination)
        });
      }
    } else {
      if (source.draggableId === 'TREE_DIMENSIONS') {
        this.setState({
          dimensionsData: dragDisableLevel(
            this.state.dimensionsData,
            data,
            source
          ),
          axes: copyLevel(this.state.axes, data, destination)
        });
      } else if (source.draggableId === destination.droppableId) {
        this.setState({
          axes: reorderHierarchy(
            this.state.axes,
            this.state.axes[destination.droppableId].items,
            source,
            destination
          )
        });
      } else {
        this.setState({
          axes: moveHierarchy(this.state.axes, source, destination)
        });
      }
    }

    this.setState({ isDisabledActionBtn: this.disableActionBtn() });
  };

  handleRemoveMeasure = (measureName, source) => {
    this.setState(
      {
        measuresData: dragEnableMeasure(this.state.measuresData, measureName),
        axes: removeMeasure(
          this.state.axes,
          this.state.axes[source.draggableId].items,
          source
        )
      },
      () => {
        this.setState({ isDisabledActionBtn: this.disableActionBtn() });
      }
    );
  };

  handleSetMeasuresDetails = details => {
    this.setState({
      measuresDetails: setMeasuresDetails(details)
    });
  };

  handleRemoveHierarchyAll = source => {
    this.setState(
      {
        dimensionsData: dragEnableLevelFromHierarchy(
          this.state.dimensionsData,
          this.state.axes[source.draggableId].items,
          source
        ),
        axes: removeHierarchy(
          this.state.axes,
          this.state.axes[source.draggableId].items,
          source
        )
      },
      () => {
        this.setState({ isDisabledActionBtn: this.disableActionBtn() });
      }
    );
  };

  handleRemoveHierarchy = source => {
    this.setState(
      {
        axes: removeHierarchy(
          this.state.axes,
          this.state.axes[source.draggableId].items,
          source
        )
      },
      () => {
        this.setState({ isDisabledActionBtn: this.disableActionBtn() });
      }
    );
  };

  handleRemoveLevel = async (levelIndex, levelName) => {
    const { selectedHierGroup } = this.state;
    const { hierarchy, source } = selectedHierGroup;

    await this.setState({
      dimensionsData: dragEnableLevel(
        this.state.dimensionsData,
        hierarchy,
        levelName
      ),
      axes: removeLevel(
        this.state.axes,
        this.state.axes[source.draggableId].items,
        levelIndex,
        source
      ),
      isDisabledActionBtn: this.disableActionBtn()
    });

    const axisItemsClone = cloneDeep(this.state.axes[source.draggableId].items);
    const levels = Object.keys(axisItemsClone[source.index].levels).map(
      (level, index) => {
        return { key: index, level };
      }
    );

    if (isEmpty(levels)) {
      // If the level property is empty, remove the hierarchy
      this.handleRemoveHierarchy(source);
      this.handleCloseSelectLevels();
    } else {
      // Update selected levels
      this.setState({
        selectedLevels: levels,
        isDisabledActionBtn: this.disableActionBtn()
      });
    }
  };

  handleSetMembers = (level, selections) => {
    const { selectedHierGroup } = this.state;
    const { source } = selectedHierGroup;

    this.setState(
      {
        axes: setMembers(
          this.state.axes,
          this.state.axes[source.draggableId].items,
          level,
          selections,
          source
        )
      },
      () => {
        const axisItemsClone = cloneDeep(
          this.state.axes[source.draggableId].items
        );

        this.setState({
          selectedHierGroup: {
            source,
            hierarchy: axisItemsClone[source.index]
          }
        });
      }
    );
  };

  handleSwapAxes = () => {
    this.setState(
      {
        axes: swapAxes(this.state.axes)
      },
      () => {
        this.setState({
          isDisabledActionBtn: this.disableActionBtn()
        });
      }
    );
  };

  handleClearAxis = axis => {
    if (axis === MEASURES) {
      this.setState(
        {
          measuresData: this.measures,
          axes: clearMeasures(this.state.axes, axis)
        },
        () => {
          this.setState({ isDisabledActionBtn: this.disableActionBtn() });
        }
      );
    } else {
      this.setState(
        {
          dimensionsData: dragEnableLevelFromAxis(
            this.state.dimensionsData,
            this.state.axes,
            axis
          ),
          axes: clearDimensions(this.state.axes, axis)
        },
        () => {
          this.setState({ isDisabledActionBtn: this.disableActionBtn() });
        }
      );
    }
  };

  handleClearAllAxesAlert = () => {
    this.setState(prevState => ({
      isOpenClearAllAxesAlert: !prevState.isOpenClearAllAxesAlert
    }));
  };

  handleClearAllAxes = () => {
    const axesName = [MEASURES, ROWS, COLUMNS, FILTER];
    const axes = axesName.reduce((obj, axis) => {
      obj[axis] = {
        ...this.state.axes[axis],
        items: []
      };

      return obj;
    }, {});

    this.setState({
      measuresData: this.measures,
      dimensionsData: this.dimensions,
      axes,
      isDisabledActionBtn: true
    });
  };

  handleShowSelectLevels = (title, levels) => {
    this.setState({
      selectedHierGroupText: title,
      selectedLevels: levels,
      isShowSelectLevels: true,
      isDisabledActionBtn: this.disableActionBtn()
    });
  };

  handleCloseSelectLevels = () => {
    this.setState({
      isShowSelectLevels: false,
      isDisabledActionBtn: this.disableActionBtn()
    });
  };

  handleShowLevels = source => {
    const axisItemsClone = cloneDeep(this.state.axes[source.draggableId].items);
    const hierGroupText = `${axisItemsClone[source.index].dimension} → ${axisItemsClone[source.index].caption}`;
    const levels = Object.keys(axisItemsClone[source.index].levels).map(
      (level, index) => {
        return { key: index, level };
      }
    );

    this.setState({
      selectedHierGroup: {
        source,
        hierarchy: axisItemsClone[source.index]
      }
    });

    this.handleShowSelectLevels(hierGroupText, levels);
  };

  buildQuery() {
    const { query } = this.props;
    const { axes, measuresDetails } = this.state;
    const axesClone = cloneDeep(axes);
    const queryClone = cloneDeep(query);

    Object.keys(axesClone).forEach(axisName => {
      if (axisName === MEASURES) {
        queryClone.queryModel.details = {
          ...measuresDetails,
          measures: axesClone[axisName].items
        };
      } else {
        queryClone.queryModel.axes[axisName].hierarchies =
          axesClone[axisName].items;
      }
    });

    return queryClone;
  }

  handleSave = () => {
    const { saveQueryDesigner, olapQueryRun, onClose } = this.props;
    const { measuresData, dimensionsData, axes, measuresDetails } = this.state;
    const buildQuery = this.buildQuery();

    saveQueryDesigner({ measuresData, dimensionsData, axes, measuresDetails });
    olapQueryRun(buildQuery);
    onClose();
  };

  render() {
    const {
      measuresData,
      dimensionsData,
      axes,
      measuresDetails,
      selectedHierGroup,
      selectedHierGroupText,
      selectedLevels,
      isShowSelectLevels,
      selectNavigationTab,
      dialogHeight,
      isDisabledActionBtn,
      isOpenClearAllAxesAlert
    } = this.state;

    return (
      <Fragment>
        <Dialog
          title="Query Designer"
          icon="edit"
          className="sku-query-designer-dialog noselect"
          canOutsideClickClose={false}
          isOpen={true}
          style={{ width: '900px', height: dialogHeight }}
          onClose={this.props.onClose}
        >
          <div className={Classes.DIALOG_BODY}>
            <Grid fluid>
              <Row>
                <Col xs>
                  <Card className="sku-card">
                    <div className="sku-card-header">
                      <ButtonGroup fill>
                        <Button
                          className="sku-fields-list sku-tab-measures"
                          text="Measures"
                          active={
                            selectNavigationTab === 'measures' ? true : false
                          }
                          onClick={this.handleChangeNavigationTab.bind(
                            this,
                            'measures'
                          )}
                        />
                        <Button
                          className="sku-fields-list sku-tab-dimensions"
                          text="Dimensions"
                          active={
                            selectNavigationTab === 'dimensions' ? true : false
                          }
                          onClick={this.handleChangeNavigationTab.bind(
                            this,
                            'dimensions'
                          )}
                        />
                      </ButtonGroup>
                    </div>
                    <div className="sku-card-body">
                      {selectNavigationTab === 'measures' ? (
                        <TreeMeasures
                          draggableId="TREE_MEASURES"
                          measures={measuresData}
                        />
                      ) : (
                        <TreeDimensions
                          draggableId="TREE_DIMENSIONS"
                          dimensions={dimensionsData}
                        />
                      )}
                    </div>
                    <div className="sku-card-remove-item">
                      <NonIdealState
                        icon={
                          <Icon
                            icon="trash"
                            iconSize={30}
                            intent={Intent.DANGER}
                          />
                        }
                        title="Remove"
                      />
                    </div>
                  </Card>
                </Col>
                <Col xs>
                  <Row>
                    <Col xs={6}>
                      <AxisMeasures
                        type={MEASURES}
                        droppableId={MEASURES}
                        title="Measures"
                        axisClassName="sku-measures-axis"
                        measures={axes[MEASURES].items}
                        measuresDetails={measuresDetails}
                        onDragEnd={this.onDragEnd}
                        removeMeasure={this.handleRemoveMeasure}
                        setMeasuresDetails={this.handleSetMeasuresDetails}
                        clearAxis={this.handleClearAxis}
                      />
                    </Col>
                    <Col xs={6}>
                      <AxisDimensions
                        type="LEVELS"
                        droppableId={ROWS}
                        title="Rows"
                        axisClassName="sku-rows-axis"
                        dimensions={axes[ROWS].items}
                        onDragEnd={this.onDragEnd}
                        showLevels={this.handleShowLevels}
                        swapAxes={this.handleSwapAxes}
                        clearAxis={this.handleClearAxis}
                      />
                    </Col>
                  </Row>
                </Col>
                <Col xs>
                  <Row>
                    <Col xs={6}>
                      <AxisDimensions
                        type="LEVELS"
                        droppableId={FILTER}
                        title="Filter"
                        axisClassName="sku-filter-axis"
                        dimensions={axes[FILTER].items}
                        onDragEnd={this.onDragEnd}
                        showLevels={this.handleShowLevels}
                        clearAxis={this.handleClearAxis}
                      />
                    </Col>
                    <Col xs={6}>
                      <AxisDimensions
                        type="LEVELS"
                        droppableId={COLUMNS}
                        title="Columns"
                        axisClassName="sku-columns-axis"
                        dimensions={axes[COLUMNS].items}
                        onDragEnd={this.onDragEnd}
                        showLevels={this.handleShowLevels}
                        swapAxes={this.handleSwapAxes}
                        clearAxis={this.handleClearAxis}
                      />
                    </Col>
                  </Row>
                </Col>
              </Row>
            </Grid>
          </div>
          <div className={Classes.DIALOG_FOOTER}>
            <div className={Classes.DIALOG_FOOTER_ACTIONS}>
              <Button
                text="Run"
                intent={Intent.DANGER}
                disabled={isDisabledActionBtn}
                onClick={this.handleSave}
              />
              <Button
                text="Clear All Axes"
                onClick={this.handleClearAllAxesAlert}
              />
              <Button text="Close" onClick={this.props.onClose} />
            </div>
          </div>
        </Dialog>

        {isShowSelectLevels && (
          <SelectLevels
            title={selectedHierGroupText}
            hierarchyGroup={selectedHierGroup}
            levels={selectedLevels}
            visible={isShowSelectLevels}
            removeLevel={this.handleRemoveLevel}
            setMembers={this.handleSetMembers}
            onClose={this.handleCloseSelectLevels}
          />
        )}

        {isOpenClearAllAxesAlert && (
          <WarningAlert
            confirmButtonText="Clear"
            message="Are you sure you want to clear all axes?"
            onCancel={this.handleClearAllAxesAlert}
            onConfirm={this.handleClearAllAxes}
          />
        )}
      </Fragment>
    );
  }
}

QueryDesignerDialog.propTypes = {
  cubes: PropTypes.array.isRequired,
  query: PropTypes.object.isRequired,
  workspace: PropTypes.object.isRequired,
  saveQueryDesigner: PropTypes.func.isRequired,
  olapQueryRun: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  cubes: state.datasources.cubes,
  ...state.query,
  ...state.workspace
});

const mapDispatchToProps = dispatch => ({
  saveQueryDesigner: data => dispatch(actionCreators.saveQueryDesigner(data)),
  olapQueryRun: query => dispatch(actionCreators.olapQueryRun(query))
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(QueryDesignerDialog);
